package cn.edu.cug.cs.gtl.oi.utils.editors;

import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.GridLayout;
import java.awt.Insets;
import java.awt.Toolkit;
import java.awt.event.*;
import java.awt.image.MemoryImageSource;
import java.util.ArrayList;
import java.util.List;

import javax.swing.*;

import com.openinventor.inventor.SbColor;
import com.openinventor.inventor.fields.SoMFColor;
import com.openinventor.inventor.fields.SoMFInt32;
import com.openinventor.inventor.fields.SoSFColor;
import com.openinventor.inventor.misc.SoBase;
import com.openinventor.inventor.nodes.SoNode;
import com.openinventor.inventor.sensors.SoNodeSensor;
import com.openinventor.inventor.viewercomponents.awt.tools.SliderPanel;

/**
 * The Color Editor let you modify interactively colors on a selected object on
 * the scene.
 *
 *
 */
public class ColorEditor extends JFrame
{

  // Sliders
  public static final int NONE = 0;
  public static final int INTENSITY = 1;
  public static final int RGB = 2;
  public static final int HSV = 3;
  public static final int RGB_V = 4;
  public static final int RGB_HSV = 5;

  /**
   * Update frequency: how often new values should be sent to the node or to the
   * editor's listener.
   */
  public enum UpdateMode
  {
    CONTINUOUS, AFTER_ACCEPT
  }

  /**
   * Images
   */
  private static final int[] RIGHT_ARROW_DATA =
      { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0xc0, 0x01, 0x00, 0xc0, 0x07, 0xf0, 0xff, 0x1f,
          0xf0, 0xff, 0x1f, 0x00, 0xc0, 0x07, 0x00, 0xc0, 0x01, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
  private static final int[] SWITCH_ARROW_DATA =
      { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x02, 0x70, 0x00, 0x0e, 0x7c, 0x00, 0x3e, 0xff, 0xff, 0xff,
          0xff, 0xff, 0xff, 0x7c, 0x00, 0x3e, 0x70, 0x00, 0x0e, 0x40, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
  private static final int[] LEFT_ARROW_DATA =
      { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x80, 0x03, 0x00, 0xe0, 0x03, 0x00, 0xf8, 0xff, 0x0f,
          0xf8, 0xff, 0x0f, 0xe0, 0x03, 0x00, 0x80, 0x03, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };

  private static final MemoryImageSource RIGHT_ARROW = new MemoryImageSource(24, 12, getARGB(RIGHT_ARROW_DATA), 0, 24);
  private static final MemoryImageSource SWITCH_ARROW =
      new MemoryImageSource(24, 12, getARGB(SWITCH_ARROW_DATA), 0, 24);
  private static final MemoryImageSource LEFT_ARROW = new MemoryImageSource(24, 12, getARGB(LEFT_ARROW_DATA), 0, 24);

  // ID list for all parts of the color editor
  private static final int R_SLIDER_ID = 0;
  private static final int G_SLIDER_ID = 1;
  private static final int B_SLIDER_ID = 2;
  private static final int H_SLIDER_ID = 3;
  private static final int S_SLIDER_ID = 4;
  private static final int V_SLIDER_ID = 5;
  private static final int SAVE_ID = 7;
  private static final int SWAP_ID = 8;
  private static final int RESTORE_ID = 9;
  private static final int ACCEPT_ID = 10;
  private static final int CONTINUOUS_ID = 11;
  private static final int MANUAL_ID = 12;
  private static final int NONE_SLIDER_ID = 13;
  private static final int INTENSITY_SLIDER_ID = 14;
  private static final int RGB_SLIDERS_ID = 15;
  private static final int HSV_SLIDERS_ID = 16;
  private static final int RGB_V_SLIDERS_ID = 17;
  private static final int RGB_HSV_SLIDERS_ID = 18;
  private static final int WYSIWYG_ID = 19;
  private static final int CLOSE_ID = 23;

  private boolean WYSIWYGmode;
  private int whichSliders;
  private SbColor baseRGB = new SbColor();
  private float[] baseHSV;
  private boolean ignoreEvent;
  private SliderPanel[] sliders = new SliderPanel[6];
  private ColorWheel wheel;
  private ColorPatch current, previous;
  private UpdateMode updateFreq;

  // attach/detach variables
  private boolean attached;
  private SoBase editNode;
  private SoSFColor colorSF;
  private SoMFColor colorMF;
  private SoNodeSensor colorSensor;
  private List<Listener> editorListeners;
  private int index;

  private SoMFInt32 colorUInt32MF;
  private float transparency;

  // GUI
  private JPanel contentPane;
  private JButton acceptButton;
  private JButton rightButton, switchButton, leftButton;
  private JCheckBoxMenuItem menuCheckNone;
  private JCheckBoxMenuItem menuCheckVal;
  private JCheckBoxMenuItem menuCheckRGB;
  private JCheckBoxMenuItem menuCheckHSV;
  private JCheckBoxMenuItem menuCheckRGBV;
  private JCheckBoxMenuItem menuCheckRGBHSV;
  private JCheckBoxMenuItem menuCheckWys;
  private JCheckBoxMenuItem menuCheckCont;
  private JCheckBoxMenuItem menuCheckMan;

  /**
   * Construct a ColorEditor.
   */
  public ColorEditor()
  {
    super();

    setTitle("Color Editor");

    addComponentListener(new VisibilityChange());
    WYSIWYGmode = false;
    whichSliders = -1;
    baseRGB.setValue(1, 0, 1);
    baseHSV = baseRGB.getHSVValue();

    attached = false;
    colorSensor = new SoNodeSensor(new FieldChanged());

    colorUInt32MF = null;
    transparency = 0; // inverse of Alpha

    editorListeners = new ArrayList<Listener>();
    ignoreEvent = false;

    buildGUI(); // build the graphic appearance

    // unmap on window manager close button
    WindowListener l = new WindowAdapter()
    {
      @Override
      public void windowClosing(WindowEvent e)
      {
        setVisible(false);
      }
    };
    addWindowListener(l);

    setWYSIWYG(true);
    setCurrentSliders(INTENSITY);
    setUpdateFrequency(UpdateMode.CONTINUOUS);
    pack();
  }

  /**
   * Construct a ColorEditor.
   */
  public ColorEditor(String name)
  {
    this();

    setTitle(name);
  }

  @Override
  protected void finalize() throws Throwable
  {
    if ( isAttached() )
      detach();
  }

  private void buildGUI()
  {
    contentPane = new JPanel();
    setContentPane(contentPane);

    GridBagLayout gbl_contentPane = new GridBagLayout();
    contentPane.setLayout(gbl_contentPane);

    JMenuBar menuBar = buildMenuBar();
    setJMenuBar(menuBar);

    buildControls();
    buildSlidersPanel();
  }

  private JMenuBar buildMenuBar()
  {
    JMenuBar menubar = new JMenuBar();

    // building the Edit menu
    JMenu menuEdit = new JMenu("Edit");
    menubar.add(menuEdit);

    boolean continuous = (updateFreq == UpdateMode.CONTINUOUS);
    ButtonGroup groupEdit = new ButtonGroup();
    menuCheckCont = new JCheckBoxMenuItem("Continuous", continuous);
    menuCheckMan = new JCheckBoxMenuItem("Manual", !continuous);
    menuCheckCont.addItemListener(new ListenEditItems(CONTINUOUS_ID));
    menuCheckMan.addItemListener(new ListenEditItems(MANUAL_ID));
    groupEdit.add(menuCheckCont);
    groupEdit.add(menuCheckMan);
    menuEdit.add(menuCheckCont);
    menuEdit.add(menuCheckMan);
    menuEdit.addSeparator();

    menuCheckWys = new JCheckBoxMenuItem("Wysiwyg", isWYSIWYG());
    menuCheckWys.addItemListener(new ListenEditItems(WYSIWYG_ID));
    menuEdit.add(menuCheckWys);
    menuEdit.addSeparator();

    JMenuItem menuClose = new JMenuItem("Close");
    menuClose.addActionListener(new ListenEditItems(CLOSE_ID));
    menuEdit.add(menuClose);

    // building the Sliders menu
    JMenu menuSliders = new JMenu("Sliders");
    menubar.add(menuSliders);

    ButtonGroup groupSliders = new ButtonGroup();
    menuCheckNone = new JCheckBoxMenuItem("None", false);
    menuCheckVal = new JCheckBoxMenuItem("Value", true);
    menuCheckRGB = new JCheckBoxMenuItem("RGB", false);
    menuCheckHSV = new JCheckBoxMenuItem("HSV", false);
    menuCheckRGBV = new JCheckBoxMenuItem("RGB V", false);
    menuCheckRGBHSV = new JCheckBoxMenuItem("RGB HSV", false);
    menuCheckNone.addItemListener(new ListenEditItems(NONE_SLIDER_ID));
    menuCheckVal.addItemListener(new ListenEditItems(INTENSITY_SLIDER_ID));
    menuCheckRGB.addItemListener(new ListenEditItems(RGB_SLIDERS_ID));
    menuCheckHSV.addItemListener(new ListenEditItems(HSV_SLIDERS_ID));
    menuCheckRGBV.addItemListener(new ListenEditItems(RGB_V_SLIDERS_ID));
    menuCheckRGBHSV.addItemListener(new ListenEditItems(RGB_HSV_SLIDERS_ID));
    groupSliders.add(menuCheckNone);
    groupSliders.add(menuCheckVal);
    groupSliders.add(menuCheckRGB);
    groupSliders.add(menuCheckHSV);
    groupSliders.add(menuCheckRGBV);
    groupSliders.add(menuCheckRGBHSV);
    menuSliders.add(menuCheckNone);
    menuSliders.add(menuCheckVal);
    menuSliders.add(menuCheckRGB);
    menuSliders.add(menuCheckHSV);
    menuSliders.add(menuCheckRGBV);
    menuSliders.add(menuCheckRGBHSV);

    return menubar;
  }

  private void buildControls()
  {
    current = new ColorPatch();
    current.setColor(baseRGB);

    GridBagConstraints gbc_current = new GridBagConstraints();
    gbc_current.fill = GridBagConstraints.BOTH;
    gbc_current.weighty = 1.0;
    gbc_current.weightx = 0.5;
    gbc_current.insets = new Insets(0, 0, 5, 5);
    gbc_current.gridx = 0;
    gbc_current.gridy = 0;
    contentPane.add(current, gbc_current);

    previous = new ColorPatch();
    previous.setColor(baseRGB);

    GridBagConstraints gbc_previous = new GridBagConstraints();
    gbc_previous.fill = GridBagConstraints.BOTH;
    gbc_previous.insets = new Insets(0, 0, 5, 5);
    gbc_previous.weighty = 1.0;
    gbc_previous.weightx = 0.5;
    gbc_previous.gridx = 1;
    gbc_previous.gridy = 0;
    contentPane.add(previous, gbc_previous);

    wheel = new ColorWheel();
    wheel.setBaseColor(baseHSV);
    wheel.addListener(new WheelChanged());

    GridBagConstraints gbc_colorWheel = new GridBagConstraints();
    gbc_colorWheel.weighty = 1.0;
    gbc_colorWheel.weightx = 1.0;
    gbc_colorWheel.gridheight = 2;
    gbc_colorWheel.fill = GridBagConstraints.BOTH;
    gbc_colorWheel.insets = new Insets(0, 0, 5, 0);
    gbc_colorWheel.gridx = 2;
    gbc_colorWheel.gridy = 0;
    contentPane.add(wheel, gbc_colorWheel);

    JPanel buttonsPanel = new JPanel();
    GridBagConstraints gbc_buttonsPanel = new GridBagConstraints();
    gbc_buttonsPanel.fill = GridBagConstraints.HORIZONTAL;
    gbc_buttonsPanel.gridwidth = 2;
    gbc_buttonsPanel.insets = new Insets(0, 0, 5, 5);
    gbc_buttonsPanel.gridx = 0;
    gbc_buttonsPanel.gridy = 1;
    contentPane.add(buttonsPanel, gbc_buttonsPanel);
    buttonsPanel.setLayout(new GridLayout(0, 3, 0, 0));

    rightButton = new JButton(new ImageIcon(Toolkit.getDefaultToolkit().createImage(RIGHT_ARROW)));
    switchButton = new JButton(new ImageIcon(Toolkit.getDefaultToolkit().createImage(SWITCH_ARROW)));
    leftButton = new JButton(new ImageIcon(Toolkit.getDefaultToolkit().createImage(LEFT_ARROW)));
    buttonsPanel.add(rightButton);
    buttonsPanel.add(switchButton);
    buttonsPanel.add(leftButton);

    acceptButton = new JButton("Accept");
    if ( updateFreq != UpdateMode.AFTER_ACCEPT )
      acceptButton.setVisible(false);

    GridBagConstraints gbc_acceptButton = new GridBagConstraints();
    gbc_acceptButton.gridwidth = 3;
    gbc_acceptButton.insets = new Insets(0, 0, 0, 5);
    gbc_acceptButton.gridx = 0;
    gbc_acceptButton.gridy = 8;
    contentPane.add(acceptButton, gbc_acceptButton);

    rightButton.addActionListener(new ListenButtons(SAVE_ID));
    switchButton.addActionListener(new ListenButtons(SWAP_ID));
    leftButton.addActionListener(new ListenButtons(RESTORE_ID));
    acceptButton.addActionListener(new ListenButtons(ACCEPT_ID));
  }

  /**
   * set colors for each slider needed and set (not)visible the sliders needed.
   * You need to call doLayout (or pack) when the editor is already visible.
   */
  private void doSliderLayout()
  {
    float[] colorRGB = baseRGB.getValue();
    int i;
    ignoreEvent = true;
    switch ( whichSliders )
    {
    case NONE :
      break;
    case INTENSITY :
      sliders[5].setVisible(true);
      break;
    case RGB :
      for ( i = 0; i < 3; i++ )
      {
        sliders[i].setSliderValue(colorRGB[i]);
        sliders[i].setVisible(true);
      }
      break;
    case HSV :
      for ( i = 3; i < 6; i++ )
      {
        sliders[i].setSliderValue(baseHSV[i - 3]);
        sliders[i].setVisible(true);
      }
      break;
    case RGB_V :
      for ( i = 0; i < 3; i++ )
      {
        sliders[i].setSliderValue(colorRGB[i]);
        sliders[i].setVisible(true);
      }
      sliders[5].setSliderValue(baseHSV[2]);
      sliders[5].setVisible(true);
      break;
    case RGB_HSV :
      for ( i = 0; i < 3; i++ )
      {
        sliders[i].setSliderValue(colorRGB[i]);
        sliders[i].setVisible(true);
      }
      for ( i = 3; i < 6; i++ )
      {
        sliders[i].setSliderValue(baseHSV[i - 3]);
        sliders[i].setVisible(true);
      }
      break;
    }
    ignoreEvent = false;
  }

  private void buildSlidersPanel()
  {
    sliders[0] = new SliderPanel(0.f, 1.f, 0.f, 2);
    sliders[1] = new SliderPanel(0.f, 1.f, 0.f, 2);
    sliders[2] = new SliderPanel(0.f, 1.f, 0.f, 2);
    sliders[3] = new SliderPanel(0.f, 1.f, 0.f, 2);
    sliders[4] = new SliderPanel(0.f, 1.f, 0.f, 2);
    sliders[5] = new SliderPanel(0.f, 1.f, 0.f, 2);

    int i;
    float[] colorRGB = baseRGB.getValue();
    for ( i = 0; i < 3; i++ )
      sliders[i].setSliderValue(colorRGB[i]);
    for ( i = 3; i < 6; i++ )
      sliders[i].setSliderValue(baseHSV[i - 3]);

    sliders[0].addInfoText("R");
    sliders[1].addInfoText("G");
    sliders[2].addInfoText("B");
    sliders[3].addInfoText("H");
    sliders[4].addInfoText("S");
    sliders[5].addInfoText("V");

    for ( i = 0; i < 6; i++ )
    {
      sliders[i].addSliderPanelListener(new ListenColorSlider(R_SLIDER_ID + i));
      sliders[i].setVisible(false);

      GridBagConstraints gbc_slider = new GridBagConstraints();
      gbc_slider.fill = GridBagConstraints.HORIZONTAL;
      gbc_slider.gridwidth = 3;
      gbc_slider.insets = new Insets(0, 0, 0, 5);
      gbc_slider.gridx = 0;
      gbc_slider.gridy = i + 2;
      contentPane.add(sliders[i], gbc_slider);
    }

    doSliderLayout();
  }

  /**
   * Adds the specified listener to receive editor's events.<br>
   * If listener l is null, no exception is thrown and no action is performed.
   * At the time dictated by {@link #setUpdateFrequency(UpdateMode)}, the
   * listener will be called with the new material.
   *
   * @param listener
   *          the editor listener
   */
  public void addListener(Listener listener)
  {
    if ( listener != null )
      editorListeners.add(listener);
  }

  /**
   * Removes the specified listener if present so that it no longer receives
   * editor's events.
   *
   * @param listener
   *          the editor listener
   * @return true if specified listener has been removed.
   */
  public boolean removeListener(Listener listener)
  {
    return editorListeners.remove(listener);
  }

  private void invokeValueChanged(SbColor rgbColor)
  {
    for ( Listener listener : editorListeners )
      listener.valueChanged(rgbColor);
  }

  /**
   * Attach the editor to a color field. It uses a nodes sensor on the specified
   * node to automatically update itself when the color is changed
   * externally.<br>
   * <br>
   * NOTE: it can only be attached to either a single field or a multiple field
   * at any given time.
   */
  public void attach(SoSFColor color, SoBase node)
  {
    if ( isAttached() )
      detach();

    if ( (color != null) && (node != null) )
    {
      setColor(color.getValue());
      colorSF = color;
      editNode = node;
      colorSensor.attach((SoNode) node);
      attached = true;
    }
  }

  /**
   * Attach the editor to a color field. It uses a nodes sensor on the specified
   * node to automatically update itself when the color is changed
   * externally.<br>
   * <br>
   * NOTE: it can only be attached to either a single field or a multiple field
   * at any given time.
   */
  public void attach(SoMFColor color, int index, SoBase node)
  {
    if ( isAttached() )
      detach();

    if ( (color != null) && (index >= 0) && (node != null) )
    {
      setColor(color.getValueAt(index));
      colorMF = color;
      editNode = node;
      colorSensor.attach((SoNode) node);
      attached = true;
    }
  }

  /**
   * Attach the editor to a color field. It uses a nodes sensor on the specified
   * node to automatically update itself when the color is changed
   * externally.<br>
   * <br>
   * NOTE: it can only be attached to either a single field or a multiple field
   * at any given time.
   */
  public void attach(SoMFInt32 color, int index, SoBase node)
  {
    if ( isAttached() )
      detach();

    if ( (color != null) && (index >= 0) && (node != null) )
    {
      SbColor theColor = new SbColor();
      float[] transparency = new float[1];
      theColor.setPackedValue(color.getValueAt(index), transparency);

      setColor(theColor);
      colorUInt32MF = color;
      this.index = index;
      editNode = node;
      colorSensor.attach((SoNode) node);
      attached = true;
    }
  }

  /**
   * Detach editor from the color field.
   */
  public void detach()
  {
    if ( !isAttached() )
      return;

    colorSensor.detach();
    editNode = null;
    colorSF = null;
    colorMF = null;
    colorUInt32MF = null;
    transparency = 0;
    attached = true;
  }

  /**
   * Check if this editor is attached to a color field.
   *
   * @return true if this editor is attached to a color field.
   */
  public boolean isAttached()
  {
    return attached;
  }

  /**
   * Set the color displayed by the color editor. <br>
   * <br>
   * NOTE: setColor() will call editor's listeners if the color differs.
   *
   */
  public void setColor(SbColor color)
  {
    if ( color.equals(baseRGB) )
      return;

    // save color
    baseRGB.setValue(color);
    baseHSV = baseRGB.getHSVValue();

    ignoreEvent = true;

    // now send the colors to the sliders/color wheel
    int i;
    float[] colorRGB = baseRGB.getValue();
    for ( i = 0; i < 3; i++ )
      sliders[i].setSliderValue(colorRGB[i]);
    for ( i = 3; i < 6; i++ )
      sliders[i].setSliderValue(baseHSV[i - 3]);
    wheel.setBaseColor(baseHSV);
    current.setColor(baseRGB);

    ignoreEvent = false;

    if ( updateFreq == UpdateMode.CONTINUOUS )
      doUpdates();
  }

  /**
   * Get the color displayed by the color editor.
   */
  public SbColor getColor()
  {
    return baseRGB;
  }

  /**
   * Set the WYSIWYG mode. (default OFF).
   */
  public void setWYSIWYG(boolean wysiwyg)
  {
    menuCheckWys.setState(wysiwyg);
  }

  private void updateWYSIWYG(boolean wysiwyg)
  {
    if ( WYSIWYGmode == wysiwyg )
      return;

    WYSIWYGmode = wysiwyg;

    // now update color wheel
    wheel.setWYSIWYG(WYSIWYGmode);
  }

  /**
   * Get the WYSIWYG mode. (default OFF).
   */
  public boolean isWYSIWYG()
  {
    return WYSIWYGmode;
  }

  /**
   * Set which slider should be displayed (default INTENSITY).
   */
  public void setCurrentSliders(int id)
  {
    int i;

    if ( whichSliders == id )
      return;

    // hide the current set of sliders
    for ( i = 0; i < 6; i++ )
      sliders[i].setVisible(false);
    whichSliders = id;

    doSliderLayout();
    switch ( id )
    {
    case NONE :
      menuCheckNone.setSelected(true);
      break;
    case INTENSITY :
      menuCheckVal.setSelected(true);
      break;
    case RGB :
      menuCheckRGB.setSelected(true);
      break;
    case HSV :
      menuCheckHSV.setSelected(true);
      break;
    case RGB_V :
      menuCheckRGBV.setSelected(true);
      break;
    case RGB_HSV :
      menuCheckRGBHSV.setSelected(true);
      break;
    }
  }

  /**
   * Get which slider should be displayed (default INTENSITY).
   */
  public int getCurrentSliders()
  {
    return whichSliders;
  }

  /**
   * Set the update frequency of when editor's listeners should be called
   * (default CONTINUOUS).
   */
  public void setUpdateFrequency(UpdateMode freq)
  {
    if ( updateFreq == freq )
      return;

    updateFreq = freq;

    // show/hide the accent button
    if ( acceptButton != null )
      acceptButton.setVisible(updateFreq == UpdateMode.AFTER_ACCEPT);

    if ( updateFreq == UpdateMode.CONTINUOUS )
    {
      // update the attached node if we switch to continuous
      doUpdates();
      menuCheckCont.setSelected(true);
    }
    else
      menuCheckMan.setSelected(true);
  }

  /**
   * Get the update frequency of when editor's listeners should be called
   * (default CONTINUOUS).
   */
  public UpdateMode getUpdateFrequency()
  {
    return updateFreq;
  }

  /**
   * Do the updates - if node is attached, update it; if callback exists, call
   * it.
   */
  private void doUpdates()
  {
    if ( attached )
    {
      if ( colorSF != null )
      {
        colorSF.setValue(baseRGB);
        if ( colorSF.isIgnored() )
          colorSF.setIgnored(false);
      }
      else if ( colorMF != null )
      {
        colorMF.set1Value(index, baseRGB);
        if ( colorMF.isIgnored() )
          colorMF.setIgnored(false);
      }
      // also handle "orderedRGBA" field
      else if ( colorUInt32MF != null )
      {
        int packedColor = baseRGB.getPackedValue(transparency);
        colorUInt32MF.set1Value(index, packedColor);
        if ( colorUInt32MF.isIgnored() )
          colorUInt32MF.setIgnored(false);
      }
    }

    invokeValueChanged(baseRGB);
  }

  private static int[] getARGB(int[] xbm)
  {
    int size = 8 * xbm.length;
    int[] argb = new int[size];

    int bits;

    for ( int i = 0; i < xbm.length; i++ )
    {
      bits = xbm[i];
      for ( int j = 0; j < 8; j++ )
      {
        int pixel = (bits & 1);
        if ( pixel != 0 )
        { // bit is set to 1 !
          argb[i * 8 + j] = 0xFF000000; // alpha=255, R=0, G=0, B=0 (black)
        }
        else
          argb[i * 8 + j] = 0x00FFFFFF; // alpha=0, R=255, G=255, B=255 (white
                                        // transparent)
        bits = (bits >> 1);
      }
    }
    return argb;
  }

  /**
   * Defines an object which listens for color editor's events.<br>
   * This object will be notified when color value has changed.
   *
   */
  public static class Listener
  {
    /**
     * Invoked when color value has changed.
     *
     * @param rgbColor
     *          new color value
     */
    public void valueChanged(SbColor rgbColor)
    {};
  }

  class FieldChanged implements Runnable
  {
    @Override
    public void run()
    {
      if ( !isVisible() )
        return;

      if ( colorSF != null )
        setColor(colorSF.getValue());
      else if ( colorMF != null )
        setColor(colorMF.getValueAt(index));
      else if ( colorUInt32MF != null )
      {
        // handle "orderedRGBA" field
        SbColor theColor = new SbColor();
        float[] f = new float[1];
        theColor.setPackedValue(colorUInt32MF.getValueAt(index), f);
        transparency = f[0];
        setColor(theColor);
      }
    }
  }

  class WheelChanged extends ColorWheel.Listener
  {
    @Override
    public void valueChanged(float[] hsv)
    {
      int i;

      // wheel can only change hue and saturation
      baseHSV[0] = hsv[0];
      baseHSV[1] = hsv[1];
      baseRGB.setHSVValue(baseHSV[0], baseHSV[1], baseHSV[2]);

      ignoreEvent = true;
      float[] colorRGB = baseRGB.getValue();
      switch ( whichSliders )
      {
      case NONE :
        break;
      case INTENSITY :
        break;
      case RGB :
      case RGB_V :
        for ( i = 0; i < 3; i++ )
          sliders[i].setSliderValue(colorRGB[i]);
        if ( whichSliders == RGB_V )
          sliders[5].setSliderValue(baseHSV[2]);
        break;
      case HSV :
        for ( i = 3; i < 6; i++ )
          sliders[i].setSliderValue(baseHSV[i - 3]);
        break;
      case RGB_HSV :
        for ( i = 0; i < 3; i++ )
          sliders[i].setSliderValue(colorRGB[i]);
        for ( i = 3; i < 6; i++ )
          sliders[i].setSliderValue(baseHSV[i - 3]);
        break;
      }
      current.setColor(baseRGB);

      ignoreEvent = false;
      if ( updateFreq == UpdateMode.CONTINUOUS )
        doUpdates();
    }
  }

  class VisibilityChange extends ComponentAdapter
  {
    @Override
    public void componentShown(ComponentEvent e)
    {
      if ( (editNode != null) && (colorSensor.getAttachedNode() == null) )
        colorSensor.attach((SoNode) editNode);
    }

    @Override
    public void componentHidden(ComponentEvent e)
    {
      colorSensor.detach();
    }
  }

  class ListenColorSlider extends SliderPanel.Listener
  {
    int i;
    int sliderId;

    public ListenColorSlider(int sliderId)
    {
      this.sliderId = sliderId;
    }

    @Override
    public void stateChanged(float value)
    {
      if ( ignoreEvent )
        return;
      ignoreEvent = true;

      float[] colorRGB;
      switch ( sliderId )
      {
      case R_SLIDER_ID :
      case G_SLIDER_ID :
      case B_SLIDER_ID :
        baseRGB.setValueAt(sliderId - R_SLIDER_ID, value);
        baseHSV = baseRGB.getHSVValue();

        colorRGB = baseRGB.getValue();
        for ( i = 0; i < 3; i++ )
          if ( i != sliderId )
            sliders[i].setSliderValue(colorRGB[i]);

        if ( whichSliders == RGB_V )
          sliders[5].setSliderValue(baseHSV[2]);
        else if ( whichSliders == RGB_HSV )
          for ( i = 3; i < 6; i++ )
            sliders[i].setSliderValue(baseHSV[i - 3]);
        wheel.setBaseColor(baseHSV);
        current.setColor(baseRGB);
        break;
      case H_SLIDER_ID :
      case S_SLIDER_ID :
      case V_SLIDER_ID :
        baseHSV[sliderId - H_SLIDER_ID] = value;
        baseRGB.setHSVValue(baseHSV);

        colorRGB = baseRGB.getValue();
        switch ( whichSliders )
        {
        case RGB_HSV :
          for ( i = 0; i < 3; i++ )
            sliders[i].setSliderValue(colorRGB[i]);
        case HSV : // and RGB_HSV
          for ( i = 3; i < 6; i++ )
            if ( i != sliderId )
              sliders[i].setSliderValue(baseHSV[i - 3]);
          break;
        case RGB_V :
          for ( i = 0; i < 3; i++ )
            sliders[i].setSliderValue(colorRGB[i]);
          break;
        case INTENSITY :
        case RGB :
        case NONE :
          break;
        }
        wheel.setBaseColor(baseHSV);
        current.setColor(baseRGB);
        break;
      }
      ignoreEvent = false;

      if ( updateFreq == UpdateMode.CONTINUOUS )
        doUpdates();
    }
  }

  class ListenEditItems implements ItemListener, ActionListener
  {
    int itemId;

    public ListenEditItems(int itemId)
    {
      this.itemId = itemId;
    }

    @Override
    public void itemStateChanged(ItemEvent e)
    {
      switch ( itemId )
      {
      case CONTINUOUS_ID :
        setUpdateFrequency(UpdateMode.CONTINUOUS);
        doLayout();
        break;
      case MANUAL_ID :
        setUpdateFrequency(UpdateMode.AFTER_ACCEPT);
        doLayout();
        break;
      case WYSIWYG_ID :
        updateWYSIWYG(!WYSIWYGmode);
        doLayout();
        break;
      case NONE_SLIDER_ID :
        setCurrentSliders(NONE);
        pack();
        break;
      case INTENSITY_SLIDER_ID :
        setCurrentSliders(INTENSITY);
        pack();
        break;
      case RGB_SLIDERS_ID :
        setCurrentSliders(RGB);
        pack();
        break;
      case HSV_SLIDERS_ID :
        setCurrentSliders(HSV);
        pack();
        break;
      case RGB_V_SLIDERS_ID :
        setCurrentSliders(RGB_V);
        pack();
        break;
      case RGB_HSV_SLIDERS_ID :
        setCurrentSliders(RGB_HSV);
        pack();
        break;
      }

    }

    @Override
    public void actionPerformed(ActionEvent e)
    {
      switch ( itemId )
      {
      case CLOSE_ID :
        setVisible(false);
        break;
      }
    }
  }

  class ListenButtons implements ActionListener
  {
    int buttonId;

    public ListenButtons(int buttonId)
    {
      this.buttonId = buttonId;
    }

    @Override
    public void actionPerformed(ActionEvent e)
    {
      SbColor col = new SbColor();

      switch ( buttonId )
      {
      case SAVE_ID :
        previous.setColor(baseRGB);
        break;
      case SWAP_ID :
      case RESTORE_ID :
        col.setValue(previous.getColor());
        if ( buttonId == SWAP_ID )
          previous.setColor(baseRGB);

        // assign new color
        setColor(col);

        if ( updateFreq == UpdateMode.CONTINUOUS )
          doUpdates();
        break;
      case ACCEPT_ID :
        doUpdates();
        break;
      }
    }
  }

}
